﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TribesPSO
{
    /// <summary>
    /// A particle implementation that moves using independant gaussian curves.  This is based on an approach described in
    /// the 2003 paper "TRIBES, a Parameter Free Particle Swarm Optimizer" by Maurice Clerc
    /// </summary>
    public sealed class IndependentGaussianParticle : Particle
    {
        private const double c = 0.71440817; //precomputed C for use in the formula used in CalculateNewPosition
        private EuclidianVector velocity;

        /// <summary>
        /// Initializes a new Particle that moves based on independent gaussian distributions.  This particle will use 
        /// the default TribesPSO.HyperspaceRandom RNG to move
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction the particle will solve to determine its "goodness"</param>
        /// <param name="initialPosition">The initial position of the particle in its search space</param>
        public IndependentGaussianParticle(IObjectiveFunction objectiveFunction, EuclidianVector initialPosition)
            : base(objectiveFunction, initialPosition)
        {
            if (objectiveFunction == null) throw new ArgumentNullException("objectiveFunction");
            velocity = EuclidianVector.Origin(objectiveFunction.Dimensions);
            this.ParticleMoved += new EventHandler<ParticleMovedEventArgs>(IndependentGaussianParticle_ParticleMoved);
        }

        /// <summary>
        /// Initializes a new Particle that moves based on independent gaussian distributions.  
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction the particle will solve to determine its "goodness"</param>
        /// <param name="initialPosition">The initial position of the particle in its search space</param>
        /// <param name="randomNumberGenerator">The random number generator that will be used to move the particle</param>
        public IndependentGaussianParticle(IObjectiveFunction objectiveFunction, EuclidianVector initialPosition, IHyperspaceRandom randomNumberGenerator)
            : base(objectiveFunction, initialPosition, randomNumberGenerator)
        {
            if (objectiveFunction == null) throw new ArgumentNullException("objectiveFunction");
            velocity = EuclidianVector.Origin(objectiveFunction.Dimensions);
            this.ParticleMoved += new EventHandler<ParticleMovedEventArgs>(IndependentGaussianParticle_ParticleMoved);
        }

        /// <summary>
        /// Calculate a new position using independant gaussian functions.
        /// </summary>
        /// <remarks>
        /// The formula used to calculate the next position of a particle using independant gaussian functions is as follows:
        /// Vd = X(t)d - X(t-1)d (Velocity is the current position minus the last position)
        /// Di = Pi,d - X(t)d   (Delta sub i is the current particle's best solution's position minus its current position)
        /// Dg = Pg,d - X(t)d   (Delta sub g is the best informer's best solution's position minus this particle's current position)
        /// X(t+1)d = X(t)d + C * (Vd + gauss_rand(Di,|Di|/2) + gauss_rand(Dg,|Dg|/2))
        /// 
        /// C = 1/(s-1+sqrt(s^2-2s))
        /// s = 2/0.97225
        /// </remarks>
        /// <param name="bestInformerSolution"></param>
        /// <returns></returns>
        protected override EuclidianVector CalculateNewPosition(Solution bestInformerSolution)
        {
            EuclidianVectorBuilder vectorBuilder = new EuclidianVectorBuilder();
            for (int n = 0; n < this.Position.Dimensions; n++)
            {
                double deltaI = this.BestSolution.Position[n] - this.Position[n];
                double deltaG = bestInformerSolution.Position[n] - this.Position[n];

                double gaussianI = RandomNumberGenerator.NextGaussian(deltaI, Math.Abs(deltaI) / 2);
                double gaussianG = RandomNumberGenerator.NextGaussian(deltaG, Math.Abs(deltaG) / 2);

                double xNext = this.Position[n] + c * (this.velocity[n] + gaussianI + gaussianG);

                vectorBuilder.AddDimensionData(xNext);
            }
            return vectorBuilder.ToEuclidianVector();
        }

        private void IndependentGaussianParticle_ParticleMoved(object sender, ParticleMovedEventArgs e)
        {
            this.velocity = e.NewPosition - e.OldPosition;
        }
    }
}
